﻿using System;
using System.Collections;
using System.IO;
using NVelocity;
using NVelocity.App;
using NVelocity.Runtime;
using SW.Lite.Infrastructure.Exception;
using SW.Lite.Infrastructure.Helpers;

namespace SW.Lite.Core.Template.NVelocity
{
    /// <summary>
    ///     Class NVelocityTemplateParser.
    /// </summary>
    public class TemplateParser : ITemplateParser
    {
        private readonly VelocityContext _context;
        private readonly VelocityEngine _engine;

        /// <summary>
        ///     Initializes a new instance of the <see cref="TemplateParser" /> class.
        /// </summary>
        public TemplateParser()
        {
            _engine = new VelocityEngine();
            _engine.SetProperty(RuntimeConstants.INPUT_ENCODING, "UTF-8");
            _engine.SetProperty(RuntimeConstants.OUTPUT_ENCODING, "UTF-8");
            _engine.SetProperty(RuntimeConstants.RESOURCE_LOADER, "file");
            _engine.SetProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, TemplateFolder);
            _engine.Init();

            _context = new VelocityContext();

            AddHelpers();
        }

        /// <summary>
        ///     Gets the absolute template folder.
        /// </summary>
        /// <remarks>
        ///     Q: why use 'AppDomain' instead of 'HttpContext'?
        ///     A: In a aysnc situation, HttpContext.Current = null, however,'AppDomain.CurrentDomain.BaseDirectory' get a not null
        ///     value
        /// </remarks>
        public string TemplateFolder
        {
            get
            {
                return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigHelper.Instance.Template.Folder,
                    ConfigHelper.Instance.Template.NVelocityFolder);
            }
        }

        /// <summary>
        ///     Parses the file.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        public string ParseFile(string filePath)
        {
            return ParseFile<string>(filePath, null);
        }

        /// <summary>
        ///     Parses the file.
        /// </summary>
        /// <typeparam name="TModel">The type of the t model.</typeparam>
        /// <param name="filePath">The file path.</param>
        /// <param name="model">The model.</param>
        /// <exception cref="CodeException">
        ///     0x001002
        ///     or
        ///     0x001001
        /// </exception>
        public string ParseFile<TModel>(string filePath, TModel model)
        {
            return ParseFile(filePath, model, null);
        }

        /// <summary>
        ///     Parses the specified template content.
        /// </summary>
        /// <param name="templateContent">Content of the template.</param>
        public string Parse(string templateContent)
        {
            return Parse<string>(templateContent, null);
        }

        /// <summary>
        ///     Parses the specified template content.
        /// </summary>
        /// <typeparam name="TModel">The type of the t model.</typeparam>
        /// <param name="templateContent">Content of the template.</param>
        /// <param name="model">The model.</param>
        /// <exception cref="CodeException">0x001011</exception>
        public string Parse<TModel>(string templateContent, TModel model)
        {
            return Parse(templateContent, model, null);
        }

        /// <summary>
        ///     Adds the helpers.
        /// </summary>
        private void AddHelpers()
        {
            //Dynamic Helper
            _context.Put("ConfigHelper", new ConfigHelper());
            _context.Put("RandomHelper", new RandomHelper());
            _context.Put("SerializeHelper", new SerializeHelper());

            //Static Class
            _context.Put("String", new StaticClassHelper<String>());
            _context.Put("Convert", new StaticClassHelper(typeof (Convert)));
        }

        /// <summary>
        ///     Parses the file.
        /// </summary>
        /// <typeparam name="TModel">The type of the t model.</typeparam>
        /// <param name="filePath">The file path.</param>
        /// <param name="model">The model.</param>
        /// <param name="context">The context.</param>
        /// <exception cref="CodeException">
        ///     0x001002
        ///     or
        ///     0x001001
        /// </exception>
        public string ParseFile<TModel>(string filePath, TModel model, Hashtable context)
        {
            if (context != null)
            {
                foreach (string item in context.Keys)
                    _context.Put(item, context[item]);
            }
            try
            {
                var vltTemplate = _engine.GetTemplate(filePath);
                if (vltTemplate == null)
                    throw new CodeException(0x001002);

                using (var writer = new StringWriter())
                {
                    vltTemplate.Merge(_context, writer);
                    return writer.ToString();
                }
            }
            catch (Exception ex)
            {
                throw new CodeException(0x001001, "", ex);
            }
        }

        /// <summary>
        ///     Parses the specified template content.
        /// </summary>
        /// <typeparam name="TModel">The type of the t model.</typeparam>
        /// <param name="templateContent">Content of the template.</param>
        /// <param name="model">The model.</param>
        /// <param name="context">The context.</param>
        /// <exception cref="CodeException">0x001011</exception>
        public string Parse<TModel>(string templateContent, TModel model, Hashtable context)
        {
            if (context != null)
            {
                foreach (string item in context.Keys)
                    _context.Put(item, context[item]);
            }

            try
            {
                using (var writer = new StringWriter())
                {
                    _engine.Evaluate(_context, writer, "atest", templateContent);
                    return writer.ToString();
                }
            }
            catch (Exception ex)
            {
                throw new CodeException(0x001011, "", ex);
            }
        }
    }
}